package com.soxing.webrain.brush.common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author luqiang
 */
@Slf4j
public class BigFileUtil {

    /**
     * 路径分隔符
     */
    public final static String SEPARATOR = "/";
    /**
     * 点分隔符
     */
    public final static String DOT = ".";
    /**
     * 中横线分隔符
     */
    public final static String SPLIT = "-";
    /**
     * 特殊字符过滤正则表达式
     */
    private static String SPECIAL_CHAR_REGEX = "[^a-zA-Z0-9\\-_.\\u4e00-\\u9fa5]";

    /**
     * 指定位置开始始写入文件
     *
     * @param tempFile 输入文件
     * @param outPath  输出文件的路径
     * @throws IOException
     */
    public static void randomAccessFile(String outPath, MultipartFile tempFile, boolean append) throws IOException {
        RandomAccessFile raFile = null;
        BufferedInputStream inputStream = null;
        try {
            File dirFile = new File(outPath);
            if (!dirFile.exists()) {
                dirFile.createNewFile();
            }
            // 以读写的方式打开目标文件
            raFile = new RandomAccessFile(dirFile, "rw");
            if (append) {
                raFile.seek(raFile.length());
            } else {
                raFile.seek(0);
            }

            inputStream = new BufferedInputStream(tempFile.getInputStream());
            byte[] buf = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buf)) != -1) {
                raFile.write(buf, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new IOException(e.getMessage());
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (raFile != null) {
                    raFile.close();
                }
            } catch (Exception e) {
                throw new IOException(e.getMessage());
            }
        }
    }

    /**
     * 文件保存
     *
     * @param outPath
     * @param tempFile
     */
    public static void saveFile(String outPath, MultipartFile tempFile) {
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(outPath);
            IOUtils.copy(tempFile.getInputStream(), outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 计算文件绝对路径
     *
     * @param dirPath  基准路径
     * @param maxFiles 每个文件夹的最大文件数
     * @param fileName 文件名
     * @return
     */
    public static String calcFilePath(String dirPath, int maxFiles, String fileName) {
        File dirFile = new File(dirPath);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
        int max = 0;
        String[] fArr = dirFile.list();
        if (fArr == null || fArr.length <= 0) {
        } else {
            for (String f : fArr) {
                try {
                    Integer i = Integer.valueOf(f);
                    if (i > max) {
                        max = i;
                    }
                } catch (Exception e) {
                    // e.printStackTrace();
                }
            }
        }
        File maxDir = new File(dirPath + SEPARATOR + max);
        if (!maxDir.exists()) {
            maxDir.mkdir();
        }
        String[] tmpArr = maxDir.list();
        if (tmpArr != null && tmpArr.length >= maxFiles) {
            max = max + 1;
            maxDir = new File(dirPath + SEPARATOR + max);
            maxDir.mkdir();
        }
        fileName = removeSpecialChar(fileName);
        return dirPath + SEPARATOR + max + SEPARATOR + fileName;
    }

    public static String removeSpecialChar(String str) {
        if (str == null || "".equals(str.trim())) {
            return str;
        }
        Pattern pattern = Pattern.compile(SPECIAL_CHAR_REGEX);
        Matcher matcher = pattern.matcher(str);
        if (matcher.find()) {
            str = matcher.replaceAll("");
        }
        return str;
    }

    /**
     * 文件md5计算
     *
     * @param file
     * @return
     */
    public static String getMd5(File file) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            return DigestUtils.md5Hex(fileInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 文件md5计算
     *
     * @param file
     * @return
     */
    public static String calcFileMd5(File file) {
        FileInputStream fileInputStream = null;
        try {
            MessageDigest MD5 = MessageDigest.getInstance("MD5");
            fileInputStream = new FileInputStream(file);
            byte[] buffer = new byte[8192];
            int length;
            while ((length = fileInputStream.read(buffer)) != -1) {
                MD5.update(buffer, 0, length);
            }
            return new String(Hex.encodeHex(MD5.digest()));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 下载服务器已存在的文件,不能断点续传
     *
     * @param response
     * @param proposeFile
     * @throws IOException
     * @throws FileNotFoundException
     */
    public static void downloadFile(HttpServletResponse response, File proposeFile) {
        OutputStream outputStream = null;
        InputStream inputStream = null;
        try {
            long fSize = proposeFile.length();
            // 下载
            response.setContentType("application/x-download");
            // response.setCharacterEncoding("UTF-8");
            String isoFileName = URLEncoder.encode(proposeFile.getName(), "UTF-8");
            // response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Length", String.valueOf(fSize));
            response.setHeader("Content-Disposition", "attachment; filename=" + isoFileName);

            outputStream = new BufferedOutputStream(response.getOutputStream());
            inputStream = new BufferedInputStream(new FileInputStream(proposeFile));
            byte[] buffer = new byte[1024];

            int len = 0;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (Exception e) {
            log.error("下载文件失败", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        File file = new File("C:\\export\\data\\combs.uestc.edu.cn\\ftp\\soxing\\vsftpd.conf");
        System.out.println(file.getParent());

        System.out.println(removeSpecialChar("vs - 给ftpd.conf"));
    }

    /**
     * 根据文件的全路径名判断文件是否存在
     *
     * @param file
     * @return
     */
    public static boolean fileExists(String file) {
        boolean fileExists = false;
        Path path = Paths.get(file);
        fileExists = Files.exists(path, new LinkOption[]{LinkOption.NOFOLLOW_LINKS});
        return fileExists;
    }

    /**
     * 文件合并
     *
     * @param folder
     * @param filename
     * @param filePath
     * @return
     */
    public static boolean merge(String folder, String filename, String filePath) {
        //先判断文件是否存在
        if (fileExists(filePath)) {
            return true;
        }
        try {
            //不存在的话，进行合并
            Files.createFile(Paths.get(filePath));
            Files.list(Paths.get(folder))
                    .filter(path -> !path.getFileName().toString().equals(filename))
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int i1 = p1.lastIndexOf("-");
                        int i2 = p2.lastIndexOf("-");
                        return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式写入文件
                            Files.write(Paths.get(filePath), Files.readAllBytes(path), StandardOpenOption.APPEND);
                            //合并后删除该块
                            Files.delete(path);
                        } catch (IOException e) {
                            log.error(e.getMessage(), e);
                        }
                    });

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            try {
                Files.delete(Paths.get(filePath));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return false;
        }
        return true;
    }

    /**
     * zip文件压缩
     * @param inputFile 待压缩文件夹/文件名
     * @param outputFile 生成的压缩包名字
     */
    public static void ZipCompress(String inputFile, String outputFile) throws Exception {
        //创建zip输出流
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outputFile));
        //创建缓冲输出流
        BufferedOutputStream bos = new BufferedOutputStream(out);
        File input = new File(inputFile);
        compress(out, bos, input,null);
        bos.close();
        out.close();
    }
    /**
     * @param name 压缩文件名，可以写为null保持默认
     */
    //递归压缩
    private static void compress(ZipOutputStream out, BufferedOutputStream bos, File input, String name) throws IOException {
        if (name == null) {
            name = input.getName();
        }
        //如果路径为目录（文件夹）
        if (input.isDirectory()) {
            //取出文件夹中的文件（或子文件夹）
            File[] flist = input.listFiles();

            if (flist.length == 0)//如果文件夹为空，则只需在目的地zip文件中写入一个目录进入
            {
                out.putNextEntry(new ZipEntry(name + "/"));
            } else//如果文件夹不为空，则递归调用compress，文件夹中的每一个文件（或文件夹）进行压缩
            {
                for (int i = 0; i < flist.length; i++) {
                    compress(out, bos, flist[i], name + "/" + flist[i].getName());
                }
            }
        } else//如果不是目录（文件夹），即为文件，则先写入目录进入点，之后将文件写入zip文件中
        {
            out.putNextEntry(new ZipEntry(name));
            FileInputStream fos = new FileInputStream(input);
            BufferedInputStream bis = new BufferedInputStream(fos);
            int len;
            //将源文件写入到zip文件中
            byte[] buf = new byte[1024];
            while ((len = bis.read(buf)) != -1) {
                bos.write(buf,0,len);
            }
            bis.close();
            fos.close();
        }
    }

    /**
     * 大文件断点续传下载
     *
     * @param request
     * @param response
     * @throws IOException
     * @throws FileNotFoundException
     */
    public static void downloadBigFile(HttpServletRequest request, HttpServletResponse response, File file)
            throws IOException, FileNotFoundException {
        if (!file.exists()) {
            log.error("文件:{}不存在", file.getAbsolutePath());
            return;
        }
        String fileName = file.getName();
        long fSize = file.length();
        // 下载
        response.setContentType("application/x-download");
        fileName = StringUtils.remove(fileName, " ");
        String isoFileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");

        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Length", String.valueOf(fSize));
        response.setHeader("Content-Disposition", "attachment; filename=" + isoFileName);
        String range = request.getHeader("Range");
        long start = 0, end = fSize - 1;
        if (StringUtils.isNotBlank(range)) {
            // 断点续传
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            try {
                String[] parts = range.split("-");
                start = Long.parseLong(parts[0].replace("bytes=", ""));
                if (parts.length > 1 && StringUtils.isNotBlank(parts[1])) {
                    end = Long.parseLong(parts[1]);
                    if (end > fSize - 1) {
                        end = fSize - 1;
                    }
                }
            } catch (NumberFormatException e) {
                log.error(request.getHeader("Range") + " is not Number!", e);
                start = 0;
                end = fSize - 1;
            }
        }
        ServletOutputStream out = null;
        BufferedOutputStream bufferOut = null;
        InputStream inputStream = null;
        try {
            out = response.getOutputStream();
            bufferOut = new BufferedOutputStream(out);
            inputStream = new FileInputStream(file);
            String contentRange = new StringBuffer("bytes ")
                    .append(new Long(start).toString())
                    .append("-")
                    .append(new Long(end).toString())
                    .append("/")
                    .append(new Long(fSize).toString())
                    .toString();
            response.setHeader("Content-Range", contentRange);
            log.info("Content-Range:" + contentRange);
            inputStream.skip(start);
            byte[] buffer = new byte[5 * 1024];
            int length = 0;
            while ((length = inputStream.read(buffer, 0, buffer.length)) != -1) {
                bufferOut.write(buffer, 0, length);
            }
            bufferOut.flush();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferOut != null) {
                try {
                    bufferOut.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
